Skip to content

feat: add @objectos/ui plugin — ObjectQL-backed view metadata service#234

Merged
hotlong merged 3 commits intomainfrom
copilot/evaluate-ui-service-implementation
Feb 10, 2026
Merged

feat: add @objectos/ui plugin — ObjectQL-backed view metadata service#234
hotlong merged 3 commits intomainfrom
copilot/evaluate-ui-service-implementation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 10, 2026

Implements the ui CoreServiceName service following the spec metadata-objectql example. View definitions are persisted in database via ObjectQL through a sys_view object rather than filesystem.

New package: packages/ui/

  • UIPlugin registers as 'ui' service, defines sys_view ObjectQL object on start, exposes CRUD:
    • saveView(name, objectName, definition) — upsert
    • loadView(name) — find by name
    • listViews(objectName) — filtered + sorted
    • deleteView(name) — by name lookup
  • Kernel compliance: healthCheck(), getManifest(), getStartupResult() following cache/workflow patterns
  • Graceful degradation: works without ObjectQL (reports degraded health), throws clear errors on CRUD attempts

Integration

  • Registered in objectstack.config.ts in the services layer
  • Added @objectos/ui: workspace:* to root dependencies

Usage

const ui = kernel.getService('ui');

// Save a view definition to database
await ui.saveView('all_accounts', 'account', {
  type: 'grid',
  columns: ['name', 'industry', 'owner'],
  sort: [{ field: 'name', order: 'asc' }],
});

// Load it back
const view = await ui.loadView('all_accounts');

// List all views for an object
const views = await ui.listViews('account');

Tests

19 tests covering lifecycle, CRUD operations (save/update/load/list/delete/nested definitions), error handling, and kernel compliance.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectos Ready Ready Preview, Comment Feb 10, 2026 4:29pm

Request Review

Copilot AI and others added 2 commits February 10, 2026 16:23
…rvice

Implements UIPlugin following the @objectstack/spec examples/metadata-objectql
pattern. View definitions are persisted in database via ObjectQL through a
sys_view object, with full CRUD (saveView, loadView, listViews, deleteView).

- packages/ui: new plugin package with types, plugin, index, tests
- objectstack.config.ts: register UIPlugin in service layer
- package.json: add @objectos/ui workspace dependency

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Evaluate implementation of UI service with ObjectQL feat: add @objectos/ui plugin — ObjectQL-backed view metadata service Feb 10, 2026
Copilot AI requested a review from hotlong February 10, 2026 16:29
@hotlong hotlong marked this pull request as ready for review February 10, 2026 16:59
Copilot AI review requested due to automatic review settings February 10, 2026 16:59
@hotlong hotlong merged commit 1d9959e into main Feb 10, 2026
10 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new @objectos/ui kernel plugin that registers the ui CoreServiceName and provides an ObjectQL-backed persistence layer for UI view metadata (sys_view), wiring it into the default stack config.

Changes:

  • Introduce new workspace package packages/ui/ with UIPlugin, public exports, types, build/test config, and Jest test suite.
  • Register UIPlugin in objectstack.config.ts and add @objectos/ui to workspace dependencies.
  • Update lockfile to include the new workspace importer/deps.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds @objectos/ui workspace linking and package dependency graph entries.
package.json Adds @objectos/ui as a workspace dependency.
objectstack.config.ts Registers new UIPlugin() in the kernel plugin list.
packages/ui/package.json Defines the new @objectos/ui package metadata, build, and test scripts.
packages/ui/tsconfig.json TypeScript build configuration for the new package.
packages/ui/jest.config.cjs Jest + ts-jest ESM configuration and coverage collection.
packages/ui/src/types.ts Defines ViewRecord and re-exports spec kernel compliance types.
packages/ui/src/plugin.ts Implements UIPlugin lifecycle, view CRUD, health/manifests, and sys_view schema registration.
packages/ui/src/index.ts Public API exports for plugin and types.
packages/ui/test/plugin.test.ts Unit tests for lifecycle, CRUD behavior, kernel compliance helpers, and error paths.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread packages/ui/src/plugin.ts
Comment on lines +53 to +58
// Obtain ObjectQL service for database access
try {
this.objectql = context.getService('objectql') ?? context.getService('data');
} catch {
// ObjectQL might not be available yet; will try again in start()
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback context.getService('data') is effectively unreachable here: if getService('objectql') throws (likely when only the CoreServiceName data exists), the ?? context.getService('data') expression never runs because control jumps to catch. This will leave this.objectql unset even when the data service is available. Split the lookups (or use hasService) so that a missing objectql service still allows resolving data.

Copilot uses AI. Check for mistakes.
Comment thread packages/ui/src/plugin.ts
Comment on lines +88 to +99
async saveView(viewName: string, objectName: string, definition: Record<string, unknown>): Promise<ViewRecord> {
this.ensureObjectQL();

const record: Omit<ViewRecord, '_id'> = {
name: viewName,
object_name: objectName,
label: (definition as any).label ?? viewName,
type: (definition as any).type ?? 'grid',
definition,
is_default: false,
is_public: true,
};
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saveView() persists definition as an object, but the registered ObjectQL schema defines definition as Field.textarea(...) (i.e., a string-like field, as used elsewhere for long text). This type mismatch is likely to break inserts/updates against a real ObjectQL backend. Consider serializing the definition (e.g., JSON) on write and parsing on read/list, and reflect that in ViewRecord (or introduce a separate persisted field like definition_json).

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +99
function createMockContext(objectql?: ReturnType<typeof createInMemoryObjectQL>): {
context: PluginContext;
kernel: any;
} {
const kernel = {
getService: jest.fn(),
services: new Map<string, any>(),
};

if (objectql) {
kernel.services.set('objectql', objectql);
}

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current tests only register the backend under the service name 'objectql', so they won't catch the case where the kernel exposes the ObjectQL engine under the CoreServiceName 'data' (which this plugin also attempts to support). Add a test that initializes the plugin with only a 'data' service registered (and no 'objectql') to ensure the lookup logic and CRUD paths work as intended.

Copilot uses AI. Check for mistakes.
Comment thread packages/ui/src/plugin.ts
Comment on lines +236 to +242
label: Field.text({ label: 'Label' }),
type: Field.select(['grid', 'kanban', 'calendar', 'timeline', 'gantt'], {
label: 'View Type',
required: true,
}),
definition: Field.textarea({ label: 'View Definition', required: true }),
is_default: Field.boolean({ label: 'Is Default' }),
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saveView() accepts any definition.type (and ViewRecord.type is string), but registerViewObject() constrains the persisted type field to a fixed Field.select([...]) list. If callers pass a different view type (or omit type and later migrate), writes can fail at the ObjectQL layer. Either validate/normalize type in saveView() to the allowed set, or loosen the schema/type definition so they stay consistent.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants